package org.jetlinks.pro.assets.annotation;

import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.jetlinks.pro.assets.AssetPermission;
import org.jetlinks.pro.assets.AssetType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.*;
import java.util.Collection;

/**
 * 在需要租户权限控制的Controller类和方法上标记此注解,实现相关数据权限控制.
 * <br>
 * 仅支持响应式的方法: 返回值为{@link Mono}或者{@link Flux}
 *
 * <pre>{@code
 *
 * @RestController
 * @RequestMapping("/test")
 * //声明数据权限控制的资产类型
 * @AssetsController(type = "test")
 * public class TestController {
 *
 * }
 *
 * }</pre>
 *
 * <ul>
 *     <li>支持带有动态查询参数{@link org.hswebframework.web.api.crud.entity.QueryParamEntity}的方法</li>
 *     <li>支持根据参数中的id进行控制,{@link AssetsController#assetIdIndex()}.(默认)</li>
 *     <li>支持根据参数中的对象属性进行控制,{@link AssetsController#assetObjectIndex()}</li>
 *     <li>支持自动绑定资产(通常在新增时),{@link AssetsController#autoBind()}.</li>
 *     <li>支持自动解绑绑定资产(通常在删除时),{@link AssetsController#unbindAll()} </li>
 * </ul>
 * <p>
 * 控制逻辑:
 * <pre>
 * 1.当方法中有参数{@link org.hswebframework.web.api.crud.entity.QueryParamEntity}时,
 *   将进行动态查询条件注入{@link org.jetlinks.pro.assets.AssetsHolder#injectQuery(QueryParamEntity, String, String)}.
 *
 * 2.当方法没有动态查询参数,默认使用第1个参数{@link AssetsController#assetIdIndex()}
 *   作为资产的ID进行权限控制{@link  org.jetlinks.pro.assets.AssetsHolder#assertPermission(String, Object, AssetPermission...)}
 *
 * 3.当设置了{@link AssetsController#assetObjectIndex()} 大于等于0时,使用对应索引的参数作为资产对象,
 *   然后通过获取资产对象中的属性{@link AssetsController#property()}作为资产ID进行权限控制
 *   {@link  org.jetlinks.pro.assets.AssetsHolder#assertPermission(String, Object, AssetPermission...)}
 *
 * 4. 当参数中同时有{@link QueryParamEntity}以及资产ID,并且想统一资产ID来进行控制时,可以设置{@link AssetsController#ignoreQuery()}
 * 来忽略条件注入
 *
 * 4. 如果指定了{@link AssetsController#autoBind()},
 *    在方法执行完成后,会将对应的资产绑定和当前用户进行绑定.{@link org.jetlinks.pro.assets.AssetsHolder#bindAssets(String, Collection)}.
 *    解绑{@link AssetsController#autoUnbind()}同理
 *
 * 5. 在进行删除操作时,建议设置{@link AssetsController#unbindAll()}为true,方法执行后会解绑此资产的所有绑定关系.
 *
 * </pre>
 *
 * @see MultiAssetsController
 * @see org.jetlinks.pro.assets.AssetsHolder
 * @see org.jetlinks.pro.assets.crud.AssetsHolderCrudController
 * @see org.jetlinks.pro.assets.crud.CorrelatesAssetsHolderCrudController
 * @see org.jetlinks.pro.assets.crud.CorrelatesAssetsHolderQueryController
 * @see org.jetlinks.pro.assets.crud.CrudAssetPermission
 * @see org.jetlinks.pro.assets.event.AssetsBindEvent
 * @see org.jetlinks.pro.assets.event.AssetsUnBindEvent
 * @see org.jetlinks.pro.assets.event.AssetsUnBindAllEvent
 * @since 1.11
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AssetsController {

    /**
     * 资产类型,类和方法上的注解不能同时为空.
     *
     * @return 资产类型标识
     * @see AssetType#getId()
     */
    String type() default "";

    /**
     * 方法参数中,资产ID对应的索引,从0开始.
     * 通常用于 根据id进行某个操作时.比如:根据id查询,根据id修改.
     *
     * @return 资产ID参数索引
     */
    int assetIdIndex() default 0;

    /**
     * 方法参数中,资产对象的索引,默认为-1,表示不使用对象来进行租户相关控制,而使用{@link AssetsController#assetIdIndex()}来处理
     *
     * @return 资产对象参数索引
     * @see org.jetlinks.pro.assets.crud.AssetsHolderCrudController#save(Flux)
     * @see org.jetlinks.pro.assets.crud.AssetsHolderCrudController#add(Mono)
     */
    int assetObjectIndex() default -1;

    /**
     * 在资产对象里表示资产ID的字段
     *
     * @return 资产ID对应的字段
     */
    String property() default "id";

    /**
     * 要求当前用户必须是资产相关持有者,比如租户,机构等,当设置为true时,没有分配资产持有信息的用户将无法访问此功能.
     *
     * @return 默认为false
     * @see AccessDenyException
     */
    boolean required() default false;

    /**
     * 是否自动绑定资产, 通用用于新增数据的时候将新增的实体类自动绑定到用户资产下
     *
     * @return 自动绑定
     */
    boolean autoBind() default false;

    /**
     * 是否自动解绑, 通用用于删除数据的时候,将资产解绑
     *
     * @return 自动绑定
     */
    boolean autoUnbind() default false;

    /**
     * 是否解绑所有的资产,当删除数据时,解绑对应权限维度的资产。
     * 当{@link AssetsController#autoUnbind()}为true是有效
     *
     * @return 是否绑定全部
     * @since 1.9
     */
    boolean unbindAll() default true;

    /**
     * 是否执行校验,通常用于新增数据的时候,设置为false则不校验传入的数据.
     *
     * @return 是否执行校验
     */
    boolean validate() default true;

    /**
     * @return 忽略处理租户权限信息
     */
    boolean ignore() default false;

    /**
     * @return 忽略处理动态查询条件
     */
    boolean ignoreQuery() default false;

    /**
     * 适用于 save or update时
     *
     * @return 当资产不存在时是否通过检查
     */
    boolean allowAssetNotExist() default false;

    /**
     * 控制数据操作权限,如果没有设置则只要有对应的资产就认为有权限
     *
     * @return 控制权限
     * @see org.jetlinks.pro.assets.AssetPermission
     * @see org.jetlinks.pro.assets.crud.CrudAssetPermission
     * @see AccessDenyException
     */
    String[] permission() default {};

    /**
     * @return 当注解在方法上时，是否合并类上的注解
     */
    boolean merge() default true;
}
